package cn.yangliu.nacos.comm.tools;

import java.io.IOException;
import java.io.Writer;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The type of nacos.
 *
 * @author 问道于盲
 * @date 2020 -01-01
 */
public class JsonUtils {

    /**
     * LOGGER is upper case
     */
    private static final Logger LOGGER = LoggerFactory
            .getLogger(JsonUtils.class);


    /**
     * current class's static member
     * The constant DEFAULT.
     */
    private static final ObjectMapper DEFAULT = new ObjectMapper();

    /**
     * current class instance's member.
     * The Object mapper.
     */
    private ObjectMapper objectMapper;

    /**
     * current class's static member
     * The constant INSTANCE.
     */
    private static final JsonUtils INSTANCE = new JsonUtils();

    /**
     * set inclusion NON_NULL set FAIL_ON_UNKNOWN_PROPERTIES
     */
    static {
        JsonUtils.getObjectMapper().configure(
                DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        JsonUtils.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }

    /**
     * Utility classes, which are collections of static members, are not meant
     * to be
     * instantiated. Even abstract utility classes, which can be extended,
     * should
     * not have public constructors.
     * <p>
     * Java adds an implicit public constructor to every class which does not
     * define
     * at least one explicitly. Hence, at least one non-public constructor
     * should be
     * defined.
     */
    private JsonUtils() {
    }

    /**
     * Method that can be used to serialize any Java value as a String.
     * <p>
     * Functionally equivalent to calling
     * {@link ObjectMapper#writeValue(Writer, Object)} with
     * {@link java.io.StringWriter} and constructing String, but more efficient.
     *
     * @param object the object
     * @return string
     */
    public static String objectToJson(Object object) {
        String ret = null;
        if (object != null) {
            try {
                ret = getObjectMapper().writeValueAsString(object);
            } catch (JsonProcessingException e) {
                LOGGER.error("Object to json failed!", e);
            }
        }
        return ret;
    }

    /**
     * Method to deserialize JSON content from given JSON content String.
     * <p>
     * Already catched IOException
     *
     * @param <T>   the type parameter
     * @param json  the json
     * @param clazz the clazz
     * @return null if IOException
     */
    public static <T> T jsonToObject(String json, Class<T> clazz) {
        T ret = null;
        if (!Strings.isNullOrEmpty(json) && clazz != null) {
            try {
                ret = getObjectMapper().readValue(json, clazz);
            } catch (IOException e) {
                LOGGER.error("Json {} to {} failed!", json, clazz.getName(), e);
            }
        }
        return ret;
    }

    /**
     * Json to object t.
     *
     * @param <T>             the type parameter
     * @param json            the json
     * @param collectionClass the collection class
     * @param elementClasses  the element classes
     * @return t
     */
    public static <T> T jsonToObject(String json, Class<?> collectionClass,
                                     Class<?>... elementClasses) {
        if (Strings.isNullOrEmpty(json)) {
            return null;
        }
        try {
            JavaType javaType = getObjectMapper().getTypeFactory()
                    .constructParametricType(collectionClass, elementClasses);
            return getObjectMapper().readValue(json, javaType);
        } catch (Exception e) {
            LOGGER.error("Json {} to object failed!", json, e);
        }
        return null;

    }

    /**
     * Whether JSON Can be deserialize or not?
     *
     * @param <T>   the type parameter
     * @param json  the json
     * @param clazz the clazz
     * @return boolean
     */
    public static <T> boolean canDeserialize(String json, Class<T> clazz) {
        boolean flag = true;
        try {
            new ObjectMapper().readValue(json, clazz);
        } catch (IOException e) {
            flag = false;
        }
        return flag;
    }

    /**
     * current class method for getter.
     * Gets object mapper.
     *
     * @return the objectMapper
     */
    public static ObjectMapper getObjectMapper() {

        if (Objects.nonNull(INSTANCE.objectMapper)){
            return INSTANCE.objectMapper;
        }

        synchronized (JsonUtils.class) {
            if (Objects.nonNull(INSTANCE.objectMapper)){
                return INSTANCE.objectMapper;
            }
            try{
                ObjectMapper objectMapper = SpringContextUtils.getBean(ObjectMapper.class);
                setObjectMapper(objectMapper);
                return objectMapper;
            }catch (Exception e){
                setObjectMapper(DEFAULT);
                return DEFAULT;
            }
        }


    }

    /**
     * current class method for setter.
     * Sets object mapper.
     *
     * @param objectMapper the objectMapper to set
     */
    public static void setObjectMapper(ObjectMapper objectMapper) {
        INSTANCE.objectMapper = objectMapper;
    }

}
